package com.ddwl.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * @author FuZhaohui
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    @Value("${spring.profiles.active}")
    private String active;

    @Override
    public int getOrder() {
//        return Constants.PRE_FILTER_ORDER_AUTH;
        return 1;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        try {
//            String resp = authApi.authorize();
//            log.warn(resp);
            List<String> headers = request.getHeaders().get("pk");
            if(!CollectionUtils.isEmpty(headers)){
                if (headers.contains("api") && !"test".equals(active)) {
                    return exchange.getResponse()
                            .writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap("非法调用".getBytes())));
                }
            }
            //TODO 调用授权服务进行认证和授权
            return chain.filter(exchange).then(Mono.fromRunnable(() -> log.warn(this.appendArgs(request, handleRequestParam(request)))));
        } catch (Exception e) {
            log.error("auth failed: " + e.getMessage());
            return exchange.getResponse()
                    .writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap("授权失败".getBytes())));
        }
    }

    /**
     * 处理Request里面参数
     *
     * @return
     */
    private String handleRequestParam(ServerHttpRequest request) {
        Map<String, List<String>> paramsMap = request.getQueryParams();
        if (paramsMap != null && !paramsMap.isEmpty()) {
            StringBuilder params = new StringBuilder();
            for (Map.Entry<String, List<String>> entry : paramsMap.entrySet()) {
                List<String> values = entry.getValue();
                if (values != null) {
                    String value;
                    if (values.size() == 1) {
                        value = values.get(0);
                    } else {
                        value = JSON.toJSONString(values, SerializerFeature.DisableCircularReferenceDetect);
                    }
                    params.append(entry.getKey() + "=" + value + "&");
                }
            }
            return params.toString();
        }
        return null;
    }

    /**
     * 拼接参数
     *
     * @param queryParam
     * @return
     */
    private String appendArgs(ServerHttpRequest request, String queryParam) {
        StringBuilder sb = new StringBuilder("\n######################### INFO #########################");
        sb.append("\n请求地址: " + request.getURI().toString());
        HttpHeaders headers = request.getHeaders();
        Map<String, String> headerMap = headers.toSingleValueMap();
        sb.append(String.format("\n请求认证: ak=%s/dk=%s/pk=%s/pt=%s", headerMap.get("ak"), headerMap.get("dk"), headerMap.get("pk"), headerMap.get("pt")));
        if (StringUtils.hasText(queryParam)) {
            sb.append("\n请求参数1: " + queryParam);
        }
        URI requestUri = request.getURI();
        String method = request.getMethodValue();
        String contentType = request.getHeaders().getFirst("Content-Type");
        //此处要排除流文件类型,比如上传的文件
        if ("POST".equals(method) && StringUtils.hasText(contentType) && !contentType.startsWith("multipart/form-data")) {
            String body = handleBody(request);
            sb.append("\n请求参数2: " + body);
            //下面将请求体再次封装写回到 request 里,传到下一级.
            URI ex = UriComponentsBuilder.fromUri(requestUri).build(true).toUri();
            ServerHttpRequest newRequest = request.mutate().uri(ex).build();
            DataBuffer bodyDataBuffer = stringBuffer(body);
            Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
            newRequest = new ServerHttpRequestDecorator(newRequest) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return bodyFlux;
                }
            };
        }
        sb.append("\n浏览信息: " + headerMap.get("User-Agent"));
        return sb.toString();
    }

    private String handleBody(ServerHttpRequest request) {
        Flux<DataBuffer> body = request.getBody();
        StringBuilder sb = new StringBuilder();
        body.subscribe(buffer -> {
            byte[] bytes = new byte[buffer.readableByteCount()];
            buffer.read(bytes);
            DataBufferUtils.release(buffer);
            String bodyString = new String(bytes, StandardCharsets.UTF_8);
            sb.append(bodyString);
        });
        return sb.toString();
    }

    private DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }
}